package com.kly.smartbi.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.kly.smartbi.bizmq.GenChartMessageProducer;
import com.kly.smartbi.common.ErrorCode;
import com.kly.smartbi.constant.CommonConstant;
import com.kly.smartbi.exception.BusinessException;
import com.kly.smartbi.exception.ThrowUtils;
import com.kly.smartbi.manager.QwenManager;
import com.kly.smartbi.manager.RedisLimiterManager;
import com.kly.smartbi.mapper.ChartMapper;
import com.kly.smartbi.model.dto.chart.ChartAddRequest;
import com.kly.smartbi.model.dto.chart.ChartQueryRequest;
import com.kly.smartbi.model.entity.Chart;
import com.kly.smartbi.model.entity.ChartResult;
import com.kly.smartbi.model.entity.User;
import com.kly.smartbi.model.enums.ChartStatusEnum;
import com.kly.smartbi.model.enums.ChartTypeEnum;
import com.kly.smartbi.model.vo.ChartVO;
import com.kly.smartbi.model.vo.UserVO;
import com.kly.smartbi.service.ChartService;
import com.kly.smartbi.service.UserService;
import com.kly.smartbi.utils.SqlUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

import static com.kly.smartbi.constant.CommonConstant.EXCEL_SUFFIX;
import static com.kly.smartbi.constant.CommonConstant.ONE_MB;

/**
 * @author admin
 * @description 针对表【chart(分析表格信息表)】的数据库操作Service实现
 * @createDate 2025-03-29 08:50:01
 */
@Service
@Slf4j
public class ChartServiceImpl extends ServiceImpl<ChartMapper, Chart>
        implements ChartService {

    @Resource
    private UserService userService;

    @Resource
    private GenChartMessageProducer genChartMessageProducer;

    @Resource
    private QwenManager qwenManager;

    @Resource
    private ThreadPoolExecutor threadPoolExecutor;

    @Resource
    private RedisLimiterManager redisLimiterManager;

    @Resource
    private TransactionTemplate transactionTemplate;


    @Override
    public void validChart(Chart chart, boolean add) {
        if (chart == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        String goal = chart.getGoal();
        String chartType = chart.getChartType();
        String chartData = chart.getChartData();

        // 创建时，参数不能为空
        if (add) {
            ThrowUtils.throwIf(StringUtils.isAnyBlank(goal, chartType, chartData), ErrorCode.PARAMS_ERROR);
        }
        // 有参数则校验
        if (StringUtils.isNotBlank(goal) && goal.length() > 150) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "目标过长");
        }

    }

    /**
     * 获取查询包装类
     *
     * @param chartQueryRequest
     * @return
     */
    @Override
    public QueryWrapper<Chart> getQueryWrapper(ChartQueryRequest chartQueryRequest) {
        QueryWrapper<Chart> queryWrapper = new QueryWrapper<>();
        if (chartQueryRequest == null) {
            return queryWrapper;
        }
        Integer id = chartQueryRequest.getId();
        String goal = chartQueryRequest.getGoal();
        String chartType = chartQueryRequest.getChartType();
        String genResult = chartQueryRequest.getGenResult();
        Long userId = chartQueryRequest.getUserId();
        Date createTime = chartQueryRequest.getCreateTime();
        Date updateTime = chartQueryRequest.getUpdateTime();
        String sortField = chartQueryRequest.getSortField();
        String sortOrder = chartQueryRequest.getSortOrder();
        Integer status = chartQueryRequest.getStatus();

        // 获取名称
        String name = chartQueryRequest.getName();

        // 拼接查询条件
        queryWrapper.eq(id != null, "id", id);
        queryWrapper.like(StrUtil.isNotBlank(name), "name", name);
        queryWrapper.like(StrUtil.isNotBlank(goal), "goal", goal);
        queryWrapper.eq(StrUtil.isNotBlank(chartType), "chartType", chartType);
        queryWrapper.like(StrUtil.isNotBlank(genResult), "genResult", genResult);
        queryWrapper.eq(userId != null, "userId", userId);
        queryWrapper.ge(ObjUtil.isNotNull(createTime), "createTime", createTime);
        queryWrapper.le(ObjUtil.isNotNull(updateTime), "updateTime", updateTime);
        queryWrapper.eq(status != null, "status", status);
        queryWrapper.orderBy(SqlUtils.validSortField(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC),
                sortField);

        return queryWrapper;
    }


    @Override
    public ChartVO getChartVO(Chart chart, HttpServletRequest request) {
        ChartVO chartVO = ChartVO.objToVo(chart);
        // 1. 关联查询用户信息
        Long userId = chart.getUserId();
        User user = null;
        if (userId != null && userId > 0) {
            user = userService.getById(userId);
        }
        UserVO userVO = userService.getUserVO(user);
        chartVO.setUser(userVO);
        return chartVO;
    }

    @Override
    public Page<ChartVO> getChartVOPage(Page<Chart> chartPage, HttpServletRequest request) {
        List<Chart> chartList = chartPage.getRecords();
        Page<ChartVO> chartVOPage = new Page<>(chartPage.getCurrent(), chartPage.getSize(), chartPage.getTotal());
        if (CollUtil.isEmpty(chartList)) {
            return chartVOPage;
        }
        // 1. 关联查询用户信息
        Set<Long> userIdSet = chartList.stream().map(Chart::getUserId).collect(Collectors.toSet());
        Map<Long, List<User>> userIdUserListMap = userService.listByIds(userIdSet).stream()
                .collect(Collectors.groupingBy(User::getId));
        // 填充信息
        List<ChartVO> chartVOList = chartList.stream().map(chart -> {
            ChartVO chartVO = ChartVO.objToVo(chart);
            Long userId = chart.getUserId();
            User user = null;
            if (userIdUserListMap.containsKey(userId)) {
                user = userIdUserListMap.get(userId).get(0);
            }
            chartVO.setUser(userService.getUserVO(user));
            return chartVO;
        }).collect(Collectors.toList());
        chartVOPage.setRecords(chartVOList);
        return chartVOPage;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ChartVO genChartByAi(ChartAddRequest chartAddRequest, MultipartFile file) {
        // 校验参数
        ThrowUtils.throwIf(chartAddRequest == null, ErrorCode.PARAMS_ERROR);
        String goal = chartAddRequest.getGoal();
        String name = chartAddRequest.getName();
        String chartType = chartAddRequest.getChartType();
        ThrowUtils.throwIf(StrUtil.hasBlank(goal, name, chartType), ErrorCode.PARAMS_ERROR);
        // 校验文件大小
        ThrowUtils.throwIf(file == null, ErrorCode.PARAMS_ERROR, "文件为空");
        long size = file.getSize();
        ThrowUtils.throwIf(size > 2 * ONE_MB, ErrorCode.PARAMS_ERROR, "文件过大");
        // 检验文件格式
        String suffix = FileUtil.getSuffix(file.getOriginalFilename());
        ThrowUtils.throwIf(!EXCEL_SUFFIX.contains(suffix), ErrorCode.PARAMS_ERROR, "不支持的文件格式");

        // redisson限流
        long loginIdAsLong = StpUtil.getLoginIdAsLong();
        redisLimiterManager.doRateLimit("genChartByAi_" + loginIdAsLong);

        // 处理文件
        StringBuilder csv = genCsvFile(file);
        ChartTypeEnum chartTypeEnum = ChartTypeEnum.getEnumByValue(chartType);
        ThrowUtils.throwIf(chartTypeEnum == null, ErrorCode.PARAMS_ERROR, "不支持的图表类型");
        chartAddRequest.setChartType(chartTypeEnum.getValue());
        // 更新数据库信息 任务执行中
        Chart chart = BeanUtil.copyProperties(chartAddRequest, Chart.class);
        chart.setUserId(loginIdAsLong);
        chart.setChartData(csv.toString());
        chart.setStatus(ChartStatusEnum.PROCESSING.getCode());
        boolean save = this.save(chart);
        ThrowUtils.throwIf(!save, ErrorCode.SYSTEM_ERROR, "保存失败");

        // 异步执行
        CompletableFuture.supplyAsync(() -> {
            ChartResult chartResult = qwenManager.genChartByQwen(chartAddRequest, csv.toString());
            ThrowUtils.throwIf(chartResult == null, ErrorCode.SYSTEM_ERROR, "AI 生成错误");
            return chartResult;
        }, threadPoolExecutor).whenComplete((chartResult, throwable) -> {
            String genChart = chartResult.getOption();
            String genResult = chartResult.getGenResult();
            ThrowUtils.throwIf(StrUtil.hasBlank(genChart, genResult), ErrorCode.SYSTEM_ERROR, "AI 生成错误");
            // 保存生成结果到数据库
            chart.setGenChart(genChart);
            chart.setGenResult(genResult);
            chart.setStatus(ChartStatusEnum.SUCCESS.getCode());
            chart.setExecMessage("AI 任务执行成功");
            boolean update = this.lambdaUpdate()
                    .eq(Chart::getId, chart.getId())
                    .eq(Chart::getUserId, chart.getUserId())
                    .update(chart);
            ThrowUtils.throwIf(!update, ErrorCode.SYSTEM_ERROR, "保存失败");
        }).exceptionally((throwable) -> {
            throwable.printStackTrace();
            chart.setStatus(ChartStatusEnum.FAILED.getCode());
            chart.setExecMessage("AI 任务执行失败");
            this.lambdaUpdate().eq(Chart::getId, chart.getId())
                    .eq(Chart::getUserId, chart.getUserId())
                    .update(chart);
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "AI 生成错误");
        });
        // 返回结果
        return BeanUtil.copyProperties(chart, ChartVO.class);
    }

    @Override
    public ChartVO genChartByAiWithMq(ChartAddRequest chartAddRequest, MultipartFile file) {
        // 校验参数
        ThrowUtils.throwIf(chartAddRequest == null, ErrorCode.PARAMS_ERROR);
        String goal = chartAddRequest.getGoal();
        String name = chartAddRequest.getName();
        String chartType = chartAddRequest.getChartType();
        ThrowUtils.throwIf(StrUtil.hasBlank(goal, name, chartType), ErrorCode.PARAMS_ERROR);
        // 校验文件大小
        ThrowUtils.throwIf(file == null, ErrorCode.PARAMS_ERROR, "文件为空");
        long size = file.getSize();
        ThrowUtils.throwIf(size > 2 * ONE_MB, ErrorCode.PARAMS_ERROR, "文件过大");
        // 检验文件格式
        String suffix = FileUtil.getSuffix(file.getOriginalFilename());
        ThrowUtils.throwIf(!EXCEL_SUFFIX.contains(suffix), ErrorCode.PARAMS_ERROR, "不支持的文件格式");

        // redisson限流
        long loginIdAsLong = StpUtil.getLoginIdAsLong();
        redisLimiterManager.doRateLimit("genChartByAi_" + loginIdAsLong);

        // 处理文件
        StringBuilder csv = genCsvFile(file);
        ChartTypeEnum chartTypeEnum = ChartTypeEnum.getEnumByValue(chartType);
        ThrowUtils.throwIf(chartTypeEnum == null, ErrorCode.PARAMS_ERROR, "不支持的图表类型");
        chartAddRequest.setChartType(chartTypeEnum.getValue());
        // 更新数据库信息 任务执行中
        Chart chart = BeanUtil.copyProperties(chartAddRequest, Chart.class);
        chart.setUserId(loginIdAsLong);
        chart.setChartData(csv.toString());
        chart.setStatus(ChartStatusEnum.PROCESSING.getCode());
        boolean save = this.save(chart);
        ThrowUtils.throwIf(!save, ErrorCode.SYSTEM_ERROR, "保存失败");

        // 消息队列
        genChartMessageProducer.sendMessage(chart.getId());

        // 返回结果
        return BeanUtil.copyProperties(chart, ChartVO.class);
    }

    @Override
    public StringBuilder genCsvFile(MultipartFile file) {
        // 获取文件
        List<Map<Integer, String>> list = null;
        try {
            list = EasyExcel.read(file.getInputStream())
                    .excelType(ExcelTypeEnum.XLSX)
                    .sheet()
                    .headRowNumber(0)
                    .doReadSync();
        } catch (IOException e) {
            log.info("excel 内容为空");
            e.printStackTrace();
        }
        if (CollUtil.isEmpty(list)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "excel 内容为空");
        }
        // 封装为 csv格式
        StringBuilder csv = new StringBuilder();
        list.stream().forEach(rowCells -> {
            List<String> rowList = new ArrayList<>(rowCells.size());
            rowCells.forEach((key, value) -> {
                rowList.add(value);
            });
            csv.append(String.join(",", rowList)).append("\n");
        });
        return csv;
    }

}


